﻿using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Core.Types.Volume;

namespace RayDen.RayEngine.Engines.PathTracer.Volume
{
    public class SingleScatteringIntegrator : VolumeIntegrator
    {
        public
            SingleScatteringIntegrator(AABB bbox, float step, float prob,
                                       RgbSpectrum inScattering, RgbSpectrum emission)
        {
            region = bbox;
            stepSize = step;
            rrProb = prob;
            sig_s = inScattering;
            lightEmission = emission;
        }

        public override float GetMaxRayLength()
        {
            return (region.Max - region.Min).Length;
        }

        public override AABB GetBounds()
        {
            return region;
        }


        public override float GetStepSize() { return stepSize; }

        public override float GetRRProbability() { return rrProb; }

        private void Sigma_s(ref Point p, out RgbSpectrum s)
        {
            s = sig_s;// *NoiseProvider.Instance.Noise(((Vector)p));
        }

        private float Density(ref Point p)
        {
            return p.z;
            //NoiseProvider.Instance.Turbulence(p.ToVec(), 8);
            //Noise((Vector)p)*Math.Min(p.y, 0f);
            //return ((Vector)p).Normalize().z;
        }

        public override void GenerateLiRays(IRayEngineScene scn, Sample sample, ref RayData ray, VolumeComputation comp)
        {
            comp.Reset();
            var scene = (RayEngineScene)(scn);

            var sigs = sig_s;
            comp.EmittedLight = lightEmission;
            float t0, t1;
            if (!region.Intersect(ray, out t0, out t1))
                return;

            if (sigs.IsBlack() || (scene.Lights.Length < 1))
            {
                float distance = t1 - t0;
                comp.EmittedLight = lightEmission * distance;
            }
            else
            {
                // Prepare for volume integration stepping
                float distance = t1 - t0;
                var nSamples = MathLab.Ceil2UInt(distance / stepSize);

                float step = distance / nSamples;
                RgbSpectrum Tr = new RgbSpectrum(1f);
                RgbSpectrum Lv = new RgbSpectrum();
                var p = ray.Point(t0);
                float offset = sample.GetLazyValue();
                t0 += offset * step;

                //Vector pPrev;
                for (var i = 0; i < nSamples; ++i, t0 += step)
                {
                    //pPrev = p;
                    p = ray.Point(t0);
                    Sigma_s(ref p, out sigs);
                    //sigs = NoiseProvider.Instance.Noise3D(((Vector)p).Normalize());
                    Lv += lightEmission;
                    if (!sigs.IsBlack() && (scene.Lights.Length) > 0)
                    {
                        // Select the light to sample
                        float lightStrategyPdf;
                        var light = scene.Lights[scene.SampleLights(sample.GetLazyValue(), out lightStrategyPdf)];

                        // Select a point on the light surface
                        float lightPdf;
                        Normal fakeNorml = new Normal(0f, 0f, 1f);
                        LightSample ls = new LightSample();
                        light.EvaluateShadow(ref p, ref fakeNorml, sample.GetLazyValue(),
                                                                sample.GetLazyValue(), sample.GetLazyValue(), ref ls);


                        var lightColor = (RgbSpectrumInfo)(ls.Spectrum);
                        lightPdf = ls.Pdf;
                        comp.Rays[comp.RayCount] = ls.LightRay;

                        if ((lightPdf > 0f) && !lightColor.IsBlack())
                        {
                            comp.ScatteredLight[comp.RayCount] =(RgbSpectrum)(Tr * sigs * lightColor * MathLab.Exp(-distance) *
                                                                 ((Density(ref p) * step) / (4f * MathLab.M_PI * lightPdf * lightStrategyPdf)));
                            comp.RayCount++;
                        }
                    }

                    comp.EmittedLight = Lv * step;
                }
            }
        }

        private
            AABB region;
        float stepSize, rrProb;
        RgbSpectrum sig_s, lightEmission;
    };
}